﻿using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using Microsoft.Synchronization.Files;
using Microsoft.WindowsAzure.Accelerator.Diagnostics;
using Microsoft.WindowsAzure.StorageClient;
          
namespace Microsoft.WindowsAzure.Accelerator.Synchronization
{
    /// <summary>
    /// Strategy for change and concurrency testing.
    /// </summary>
    public enum VarianceCheck
    {
        /// <summary>
        /// Always perform the storage action.
        /// </summary>
        None = 0,
        /// <summary>
        /// Perform storage action only if the last modified time matches.
        /// </summary>
        LastModifiedUtc = 1,
        /// <summary>
        /// Perform storage action only if the eTag has not changed.
        /// </summary>
        eTag = 2
    }

    /// <summary>
    /// Represents a Windows Azure blob.
    /// </summary>
    public class CloudSyncBlob
    {
        public CloudBlob Blob { get; set; }
        private CloudBlobDirectory CloudDirectory { get; set; }
        private CloudBlobClient ServiceClient { get { return CloudDirectory.ServiceClient; } }

#region | BASE CONSTRUCTORS

        /// <summary>
        /// Initializes a new instance of the <see cref="T:Microsoft.WindowsAzure.StorageClient.CloudBlob"/>
        /// class using an absolute URI to the blob.
        /// </summary>
        /// <param name="blob">The BLOB.</param>
        /// <param name="directory">The directory.</param>
        public CloudSyncBlob(CloudBlob blob, CloudBlobDirectory directory)
        {
            CloudDirectory = directory;
            Blob = blob;
        }
        
        /// <summary>
        /// Initializes a new instance of the <see cref="T:Microsoft.WindowsAzure.StorageClient.CloudBlob"/>
        /// class using a relative URI to the blob.
        /// </summary>
        /// <param name="blobUri">The relative URI to the blob, beginning with the container name.</param>
        /// <param name="directory">The directory.</param>
        public CloudSyncBlob(String blobUri, CloudBlobDirectory directory)
        {
            CloudDirectory = directory;
            Blob = directory.GetBlobReference(blobUri);
        }

#endregion
#region | FILEDATA

        /// <summary>
        /// Gets the file data.
        /// </summary>
        /// <value>The file data.</value>
        public FileData FileData
        {
            get
            {
                return new FileData(
                    PathWithName,
                    FileAttributes,
                    CreationTime,
                    LastAccessTime,
                    LastWriteTime,
                    IsDirectory ? 0 : FileSize
                    );
            }
            set
            {
                FileName = value.Name;
                PathWithName = value.RelativePath;
                FileSize = value.Size;
                FileAttributes = value.Attributes;
                CreationTime = value.CreationTime.ToUniversalTime();
                LastAccessTime = value.LastAccessTime.ToUniversalTime();
                LastWriteTime = value.LastWriteTime.ToUniversalTime();
                IsMetaDirectory = value.IsDirectory;
                Blob.Properties.ContentType = MimeTypes.Instance[System.IO.Path.GetExtension(value.Name)];
            }
        }

#endregion
#region | PROPERTIES

        /// <summary>
        /// Gets the file name.
        /// </summary>
        public String FileName
        {
            get { return Blob.Metadata[CloudSyncKeys.FileNameKey]; }
            set { Blob.Metadata[CloudSyncKeys.FileNameKey] = value; }
        }

        /// <summary>
        /// Gets the file path with name.
        /// </summary>
        public String PathWithName
        {
            get { return Blob.Metadata[CloudSyncKeys.PathWithNameKey]; }
            set { Blob.Metadata[CloudSyncKeys.PathWithNameKey] = value; }
        }

        /// <summary>
        /// Gets the file path with name.
        /// </summary>
        public String RelativePath
        {
            get { return Blob.Metadata[CloudSyncKeys.RelativePathKey]; }
            set { if (!String.IsNullOrEmpty(value)) Blob.Metadata[CloudSyncKeys.RelativePathKey] = value; }
        }

        /// <summary>
        /// Gets the file attributes.
        /// </summary>
        public FileAttributes FileAttributes
        {
            get { return (FileAttributes)Int64.Parse(Blob.Metadata[CloudSyncKeys.AttributesKey]); }
            set { Blob.Metadata[CloudSyncKeys.AttributesKey] = ((Int64)value).ToString(); } 
        }

        /// <summary>
        /// Gets a value indicating whether this instance is directory.
        /// </summary>
        public Boolean IsDirectory
        {
            get { return (FileAttributes & FileAttributes.Directory) == FileAttributes.Directory; }
        }

        /// <summary>
        /// Gets a value indicating whether this instance is a directory according to metadata.
        /// </summary>
        public Boolean IsMetaDirectory
        {
            get { return Boolean.Parse(Blob.Metadata[CloudSyncKeys.IsDirectory]); }
            set { Blob.Metadata[CloudSyncKeys.IsDirectory] =  value.ToString(); }
        }

        /// <summary>
        /// Gets the creation time.
        /// </summary>
        public DateTime CreationTime
        {
            get { return new DateTime(Blob.Metadata[CloudSyncKeys.CreationTimeKey].As<Int64>(), DateTimeKind.Utc); }
            set { Blob.Metadata[CloudSyncKeys.CreationTimeKey] = value.ToUniversalTime().Ticks.ToString(); }
        }

        /// <summary>
        /// Gets the last access time.
        /// </summary>
        public DateTime LastAccessTime
        {
            get { return new DateTime(Blob.Metadata[CloudSyncKeys.LastAccessTimeKey].As<Int64>(), DateTimeKind.Utc); }
            set { Blob.Metadata[CloudSyncKeys.LastAccessTimeKey] = value.ToUniversalTime().Ticks.ToString(); }
        }

        /// <summary>
        /// Gets the last write time.
        /// </summary>
        public DateTime LastWriteTime
        {
            get { return new DateTime(Blob.Metadata[CloudSyncKeys.LastWriteTimeKey].As<Int64>(), DateTimeKind.Utc); }
            set { Blob.Metadata[CloudSyncKeys.LastWriteTimeKey] = value.ToUniversalTime().Ticks.ToString(); }
        }

        /// <summary>
        /// Gets or sets the size of the file.
        /// </summary>
        public Int64 FileSize
        {
            get { return Int64.Parse(Blob.Metadata[CloudSyncKeys.SizeKey]); }
            set { Blob.Metadata[CloudSyncKeys.SizeKey] = value.ToString(); }
        }

        /// <summary>
        /// Gets the properties.
        /// </summary>
        public BlobProperties Properties
        {
            get { return Blob.Properties;  }
        }

        /// <summary>
        /// Gets or sets the virtual path relative to the root CloudBlobDirectory.
        /// </summary>
        /// <value>The virtual path.</value>
        public String VirtualPath
        {
            get
            {
                var vp = Blob.Uri.ToString().Substring(CloudDirectory.Uri.ToString().Length).TrimEnd(' ', '\\', '/');
                Trace.TraceInformation("Virtual Path Request:  {0}", vp);
                return vp; 
            }
        }

        /// <summary>
        /// Gets or sets the virtual path relative to the root CloudBlobDirectory.
        /// </summary>
        /// <value>The virtual path.</value>
        public String LocalPath
        {
            get
            {
                var lp = VirtualPath.SetPathChar('\\');
                Trace.TraceInformation("Local Path Request:  {0}", lp);
                return lp;
            }
        }

#endregion
#region | GET

        /// <summary>
        /// Downloads the blob if it matches the expected last modification date.
        /// </summary>
        /// <param name="targetPath">The target download file path for the blob.</param>
        /// <param name="accessCondition">The access condition.</param>
        public void Get(String targetPath, AccessCondition accessCondition)
        {
            try
            {
                BlobRequestOptions options = new BlobRequestOptions()
                {
                    AccessCondition = accessCondition,
                    BlobListingDetails = BlobListingDetails.Metadata
                };
                
                using (var ds = new FileStream(targetPath, FileMode.Create))
                {
                    Blob.DownloadToStream(ds, options);
                }

                File.SetAttributes(targetPath, FileAttributes);
                File.SetLastWriteTimeUtc(targetPath, this.LastWriteTime);
                File.SetLastAccessTimeUtc(targetPath, this.LastAccessTime);
                File.SetCreationTimeUtc(targetPath, this.CreationTime);
            }
            catch (StorageException e)
            {
                if (e.ErrorCode == StorageErrorCode.BlobNotFound)
                {
                    throw new ApplicationException("Concurrency Variance:  blob download aborted; blob not found.", e);
                }
                if (e.ErrorCode == StorageErrorCode.ConditionFailed)
                {
                    throw new ApplicationException("Concurrency Variance:  blob download aborted; blob has changed.", e);
                }
                throw;
            }
        }

#endregion
#region | DELETE

        /// <summary>
        /// Deletes the blob if it matches the expected eTag or last modification date.
        /// </summary>
        /// <param name="condition">The condition.</param>
        public void Delete(AccessCondition condition)
        {
            if (this.IsMetaDirectory)
            {
                CloudBlobDirectory directoryBlob = Blob.ServiceClient.GetBlobDirectoryReference(Blob.Uri.AbsolutePath);
                if (directoryBlob.ListBlobs(new BlobRequestOptions { UseFlatBlobListing = true }).FirstOrDefault() != null )
                {
                    throw new ApplicationException("Concurrency Variance:  blob delete aborted; directory contains files.");
                }
            }
            Blob.Delete(new BlobRequestOptions { AccessCondition = condition });
        }

#endregion
#region | SAVE

        /// <summary>
        /// Saves the specified stream to the blob.
        /// </summary>
        /// <param name="stream">The stream.</param>
        /// <param name="condition">The condition.</param>
        public void Save(Stream stream, AccessCondition condition)
        {   
            stream = stream ?? new MemoryStream();
            stream.Seek(0, SeekOrigin.Begin);
            Blob.UploadFromStream(stream, new BlobRequestOptions { AccessCondition = condition });
            Blob.SetProperties();
        }

#endregion
#region | DEBUG


        /// <summary>
        /// Writes the trace.
        /// </summary>
        /// <param name="file">The file.</param>
        public void WriteTrace(FileData file)
        {
            var t = new
                        {
                            blob = new 
                            { 
                                LastWriteTimeUtc = LastWriteTime.ToUniversalTime(),
                                LastWriteTimeTicks = LastWriteTime.ToUniversalTime().Ticks
                            },
                            file = new
                                       {
                                           LastWriteTimeUtc = file.LastWriteTime.ToUniversalTime(),
                                           LastWriteTimeTicks = file.LastWriteTime.ToUniversalTime().Ticks
                                       }

                        };

            Trace.TraceInformation("Concurrency Variance:\n\t{0}", t.ToTraceString("\n\r", "\t//i| "));
        }
#endregion

    }
}